package ruler;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


public class RulerModel implements IRulerModel {
	private static final double MIN_TICK_DISTANCE = 5;
	private static final int TOTAL_DEGREE = 360;
	private static final double WIDTH_NORMALIZATION_FACTOR = 1;
	private final Set<IModelDataListener> listeners;
	private final int minorDegreeInterval;
	private final int majorDegreeInterval;
	
	private List<RulerTick> rulerTicks;
	private double course;
	
	public RulerModel(int minorDegreeInterval, int majorDegreeInterval) {
		assert minorDegreeInterval < majorDegreeInterval;
		assert majorDegreeInterval % minorDegreeInterval == 0;
		
		this.minorDegreeInterval = minorDegreeInterval;
		this.majorDegreeInterval = majorDegreeInterval;
		this.listeners = new HashSet<IModelDataListener>();
		this.rulerTicks = new ArrayList<RulerTick>();
		
		for(int i=-180;i<=180;i+=minorDegreeInterval) {
			rulerTicks.add(createRulerTick(i));
		}		
	}	

	@Override
	public void setRulerParams(int rulerCourse, double rulerWidth) {
		this.course = rulerCourse;
		int shift = (int) Math.ceil(course/minorDegreeInterval);
		if(shift > 0) {
			for (int i = 0; i < shift; i++) {
				rulerTicks.remove(0);
				int degreValue = rulerTicks.get(rulerTicks.size() - 1).getDegree() + minorDegreeInterval;
				rulerTicks.add(createRulerTick(degreValue));
			}
		} else {
			for (int i = 0; i < -shift; i++) {
				rulerTicks.remove(rulerTicks.size() - 1);
				int degreValue = rulerTicks.get(0).getDegree() - minorDegreeInterval;
				rulerTicks.add(0, createRulerTick(degreValue));
			}
		}
		rulerTicks.add(0,new PortRulerTick());
		rulerTicks.add(new StarboardRulerTick());
		
		updateRulerWidth(rulerWidth);
	}

	@Override
	public void updateRulerWidth(double rulerWidth) {
		double normalizedWidth = rulerWidth * WIDTH_NORMALIZATION_FACTOR; 
		double offsetPixel = calculateOffsetPixel(normalizedWidth);
		int lastTickIndex = rulerTicks.size() - 1; 
		
		rulerTicks.get(0).setPosition(0d);
		for (int i=1;i<lastTickIndex;i++) {
			double position = offsetPixel + (i - 1) * minorDegreeInterval * normalizedWidth / TOTAL_DEGREE;
			if(position >= 0d && position <= normalizedWidth) {
				rulerTicks.get(i).setPosition(offsetPixel + (i - 1) * minorDegreeInterval * normalizedWidth / TOTAL_DEGREE);
			} else {
				rulerTicks.remove(i); 
				lastTickIndex--;
				i--;
			}
		}
		rulerTicks.get(lastTickIndex).setPosition(normalizedWidth);
		
		clearTextValueForCloseMajors();
	}

	private void clearTextValueForCloseMajors() {
		final int lastTickIndex = rulerTicks.size() - 1;
		final double portTickPosition = rulerTicks.get(0).getPosition();
		final double portNearTickPosition = rulerTicks.get(1).getPosition();
		final double starboardTickPosition = rulerTicks.get(lastTickIndex).getPosition();
		final double startboardNearTickPosition = rulerTicks.get(lastTickIndex - 1).getPosition();
		
		if(Math.abs(portNearTickPosition - portTickPosition) < MIN_TICK_DISTANCE ) {
			rulerTicks.get(1).setDegreeText("");
		}
		
		if(Math.abs(startboardNearTickPosition - starboardTickPosition) < MIN_TICK_DISTANCE ) {
			rulerTicks.get(lastTickIndex - 1).setDegreeText("");
		}
		
	}

	private double calculateOffsetPixel(double rulerWidth) {
		double offsetPixel = (minorDegreeInterval - (course % minorDegreeInterval))
				% minorDegreeInterval * (rulerWidth / TOTAL_DEGREE);
		return offsetPixel;
	}
	
	@Override
	public List<RulerTick> getRulerTicks() {
		return rulerTicks;
	}
	
	public void addModelDataListener(IModelDataListener listener) {
		listeners.add(listener);
	}
	
	public void removeModelDataListener(IModelDataListener listener) {
		listeners.remove(listener);
	}

	private RulerTick createRulerTick(int degreValue) {
		final RulerTick rulerTick;
		if(degreValue % majorDegreeInterval == 0) {
			rulerTick = new MajorRulerTick(degreValue);
		} else {
			rulerTick = new MinorRulerTick(degreValue);
		}	
		return rulerTick;
	}

}
